package com.etl.extract;

import com.eksframework.commons.core.constant.ConstString;
import com.etl.exception.DatabaseException;
import com.etl.sys.entity.SysDatabase;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * @author: chenfenghai
 * @version: 1.0
 * @date: 2020-10-15 13:36
 */
@Slf4j
@Component
public class DatabaseBuilder {

    @Autowired
    HikariConfig hikariConfig;
    @Autowired
    SqlSessionFactory sqlSessionFactory;
    @Autowired
    ScheduleTaskBuilder scheduleTaskBuilder;

    /**
     * 数据库线程池名称前缀
     */
    private static final String POOL_NAME_PREFIX = "pool_";
    /**
     * 数据库KEY与数据源的KEY-VALUE键值对，保存有效的数据源
     */
    private static final Map<String, HikariDataSource> HIKARI_DATA_SOURCE_MAP
            = Maps.newConcurrentMap();

    /**
     * 加载数据库定时器，定时加载已失效的数据源并保存在HIKARI_DATA_SOURCE_MAP中
     */
    public void loadDatabaseTimer() {
        log.debug("Load database timer starting...");
        scheduleTaskBuilder.getScheduleTaskList().stream()
                .filter(this::checkDataSourceNotExists)
                .forEach(this::loadDatabase);
        log.debug("Load database timer completed.");
    }

    /**
     * 加载数据库
     *
     * @param scheduleTask 调度任务信息
     */
    public void loadDatabase(ScheduleTask scheduleTask) {
        if(Objects.isNull(scheduleTask.getDatabase())){
            return;
        }
        String databaseKey = getDatabaseKey(scheduleTask);
        try {
            HikariDataSource dataSource = HIKARI_DATA_SOURCE_MAP.get(databaseKey);
            if(Objects.nonNull(dataSource)){
                dataSource.close();
                HIKARI_DATA_SOURCE_MAP.remove(databaseKey);
            }
            HIKARI_DATA_SOURCE_MAP.put(databaseKey, createDataSource(scheduleTask.getDatabase()));
        } catch (Exception e) {
            log.error("Initializing HikariDataSource exception: ", e);
        }
    }

    /**
     * 获取数据库缓存KEY，格式为：cinemaId_databaseId
     * @param scheduleTask 调度任务信息
     * @return String
     */
    public String getDatabaseKey(ScheduleTask scheduleTask){
        return StringUtils.joinWith(ConstString.UNDERLINE,
                scheduleTask.getCinema().getCinemaId(),
                scheduleTask.getDatabase().getDatabaseId());
    }

    /**
     * 创建数据源
     *
     * @param sysDatabase 数据库记录
     * @return HikariDataSource
     */
    public HikariDataSource createDataSource(SysDatabase sysDatabase) {
        HikariConfig config = new HikariConfig();
        hikariConfig.copyStateTo(config);
        config.setPoolName(StringUtils.join(POOL_NAME_PREFIX,
                sysDatabase.getDatabaseId()));
        config.setDriverClassName("com.mysql.cj.jdbc.Driver");
        config.setJdbcUrl(StringUtils.trimToEmpty(sysDatabase.getJdbcUrl())
                .replace("jdbc:p6spy:mysql:", "jdbc:mysql:"));
        config.setUsername(StringUtils.trimToEmpty(sysDatabase.getUsername()));
        config.setPassword(StringUtils.trimToEmpty(sysDatabase.getPassword()));
        return new HikariDataSource(config);
    }

    /**
     * 获取数据库连接
     *
     * @param scheduleTask 调度任务
     * @return Connection
     */
    public Connection getConnection(ScheduleTask scheduleTask) {
        String databaseKey = getDatabaseKey(scheduleTask);
        try {
            Optional<HikariDataSource> optionalDataSource = getDataSource(databaseKey);
            return optionalDataSource.orElseThrow(DatabaseException::new).getConnection();
        } catch (SQLException e) {
            HIKARI_DATA_SOURCE_MAP.remove(databaseKey);
            throw new DatabaseException("Get database connection throw SQLException: ", e);
        }
    }

    /**
     * 根据ID及参数获取SQL
     *
     * @param id              SQL id
     * @param parameterObject 参数
     * @return String
     */
    public String getSql(String id, Object parameterObject) {
        return sqlSessionFactory.getConfiguration()
                .getMappedStatement(id).getBoundSql(parameterObject)
                .getSql();
    }

    /**
     * 查询数据
     *
     * @param scheduleTask 调度任务
     * @param sqlQuery     SQL查询
     * @return List
     */
    public List<Map<String, String>> queryData(
            ScheduleTask scheduleTask, SqlQuery sqlQuery) {
        List<Map<String, String>> objectList = Lists.newArrayList();
        String sql = getSql(sqlQuery.getId(), sqlQuery.getParameterObject());
        Stopwatch stopwatch = Stopwatch.createStarted();
        try (
                Connection conn = getConnection(scheduleTask);
                PreparedStatement statement = conn.prepareStatement(sql);
                ResultSet rs = statement.executeQuery()
        ) {
            int col = rs.getMetaData().getColumnCount();
            while (rs.next()) {
                Map<String, String> resultMap = Maps.newHashMap();
                for (int i = 1; i <= col; i++) {
                    resultMap.put(rs.getMetaData().getColumnLabel(i), rs.getString(i));
                }
                objectList.add(resultMap);
            }
        } catch (Exception e) {
            throw new DatabaseException("Query data throw Exception", e);
        }
        // 打印SQL
        log.debug("Consume Time：{} ms  Query Count: {}  Select Id: {}\n Execute SQL：{}\n",
                stopwatch.stop().elapsed(TimeUnit.MILLISECONDS), objectList.size(),
                sqlQuery.getId(), sql.replaceAll("[\\s]+", " "));
        return objectList;
    }

    /**
     * 获取数据源
     *
     * @param databaseKey 数据库缓存KEY
     * @return Optional
     */
    public Optional<HikariDataSource> getDataSource(final String databaseKey) {
        return Objects.isNull(databaseKey) ? Optional.empty() :
                Optional.ofNullable(HIKARI_DATA_SOURCE_MAP.get(databaseKey));
    }

    /**
     * 检查数据源是否存在
     *
     * @param databaseKey 数据库缓存KEY
     * @return boolean
     */
    public boolean checkDataSourceExists(final String databaseKey) {
        return Objects.nonNull(databaseKey)
                && HIKARI_DATA_SOURCE_MAP.containsKey(databaseKey);
    }

    /**
     * 检查数据源是否不存在
     *
     * @param scheduleTask 调度任务信息
     * @return boolean 如果存在则返回false，如果不存在则返回true
     */
    public boolean checkDataSourceNotExists(final ScheduleTask scheduleTask) {
        if(Objects.isNull(scheduleTask.getDatabase())){
            return true;
        }
        String databaseKey = getDatabaseKey(scheduleTask);
        return !checkDataSourceExists(databaseKey);
    }
}
